﻿using BITUI.Element;
using BITUI.Tile;
using SDL2;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Rect = SDL2.SDL.SDL_Rect;
namespace BITUI
{
    public sealed class Window : Element.IWindowRender, IWindowEvent
    {
        const int TilePixelSize = 8;
        internal Window(WindowOption option)
        {
            this.option = option;

            float scale = TilePixelSize * option.scale;
            int width = (int)(option.tileWidth * scale);
            int height = (int)(option.tileHeight * scale);
            if (this.option.linear)
                SDL.SDL_SetHint(SDL.SDL_HINT_RENDER_SCALE_QUALITY, "linear");
            else
                SDL.SDL_SetHint(SDL.SDL_HINT_RENDER_SCALE_QUALITY, "nearest");

            this.windowPtr = SDL.SDL_CreateWindow(option.title, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, width, height, SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN | SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE);
            this.id = SDL.SDL_GetWindowID(this.windowPtr);

            this.render = SDL.SDL_CreateRenderer(this.windowPtr, 0, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED | SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC | SDL.SDL_RendererFlags.SDL_RENDERER_TARGETTEXTURE);
            queueLimit = new Stack<LimitRect>();


            //win.InitSurface(width, height);

            //创建一个零号bank
            this.CreateBank(Tile.TileTool.GetNesPalette(), Tile.TileTool.GetNesTransparentColorIndex());
            this.OnResize();
        }

        List<Tile.Bank> banks;//Bank是用来管理Tile的部件
        public Tile.Bank CreateBank(SDL.SDL_Color[] palette, int transparentColorIndex)
        {
            if (banks == null)
                banks = new List<Tile.Bank>();
            Tile.Bank b = new Tile.Bank(palette, transparentColorIndex, this.render);
            b.bankID = banks.Count;
            banks.Add(b);
            return b;
        }
        public Tile.Bank GetBank(int bankID)
        {
            if (banks == null || banks.Count <= bankID)
                return null;
            return banks[bankID];
        }
        public WriteableTile CreateWriteableTile(SDL.SDL_Color[] palette, int width, int height)

        {
            WriteableTile t = new WriteableTile
                (this.render, palette, width, height);
            return t;
        }
        WindowOption option;


        //private void InitSurface(int winwidth, int winheight)
        //{
        //    //输出表面
        //    var tagetsurface = SDL.SDL_GetWindowSurface(this.windowPtr);
        //    SDL.SDL_Surface tt = Marshal.PtrToStructure<SDL.SDL_Surface>(tagetsurface);
        //    var ttformat = Marshal.PtrToStructure<SDL.SDL_PixelFormat>(tt.format);


        //    var scale = (int)option.tilesize;
        //    int width = tilewidth * scale;
        //    int height = tileheight * scale;
        //    if (option.scaleMode == WindowOption_ScaleMode.FixTileSize)
        //    {
        //        if (this.surfaceFix != IntPtr.Zero)
        //        {
        //            //fix surface mode do not resize surface
        //            return;
        //        }
        //        width = tilewidth * scale;
        //        height = tileheight * scale;


        //    }
        //    else if (option.scaleMode == WindowOption_ScaleMode.AutoTileSize)
        //    {
        //        var targw = (int)(winwidth / option.scale / scale);
        //        var targeth = (int)(winheight / option.scale / scale);


        //        width = targw * scale;
        //        height = targeth * scale;

        //        //根据window尺寸，反推surface的尺寸，如果有旧的surface要丢弃，就丢弃
        //        if (this.surfaceFix != IntPtr.Zero)
        //        {
        //            if (this.tilewidth == targw && this.tileheight == targeth)
        //            {//窗口尺寸没变
        //                return;
        //            }
        //            else
        //            {
        //                this.tilewidth = targw;
        //                this.tileheight = targeth;
        //            }
        //        }

        //    };


        //    if (this.surfaceFix != IntPtr.Zero)
        //    {
        //        SDL.SDL_FreeSurface(this.surfaceFix);
        //        this.surfaceFix = IntPtr.Zero;
        //        this.surface.Free();
        //        this.surface = null;
        //    }

        //    this.surfaceFix = SDL.SDL_CreateRGBSurfaceWithFormat(0, width, height, ttformat.BitsPerPixel, ttformat.format);
        //    var surfacePtr = SDL.SDL_CreateRGBSurfaceWithFormat(0, width, height, option.indexColor ? 8 : 32, option.indexColor ? SDL.SDL_PIXELFORMAT_INDEX8 : SDL.SDL_PIXELFORMAT_ARGB8888);
        //    this.surface = Surface.Create(surfacePtr, width, height, option.indexColor ? option.palette : null);



        //}
        public void Close()
        {
            //SDL.SDL_FreeSurface(this.surfaceFix);
            //this.surfaceFix = IntPtr.Zero;
            //this.surface.Free();
            SDL.SDL_DestroyRenderer(this.render);
            this.render = IntPtr.Zero;
            SDL.SDL_DestroyWindow(this.windowPtr);
            this.windowPtr = IntPtr.Zero;
        }
        public uint id
        {
            get;
            private set;
        }
        public IntPtr windowPtr;

        private IntPtr render;
        //private IntPtr tex;
        public Element.IElement2DGroup _RootElement;
        public Element.IElement2DGroup RootElement
        {
            get
            {
                return _RootElement;
            }
            set
            {
                if (value != null && value.Parent != null)
                {
                    value.Parent.RemoveChild(value);
                }
                _RootElement = value;
                _RootElement.OnParentResize(this.option.tileWidth, this.option.tileHeight);
            }
        }
        public void UpdateWindow()
        {
            SDL.SDL_Rect rect = new SDL.SDL_Rect() { x = 0, y = 0, w = 500, h = 1000 };
            SDL.SDL_GetRendererOutputSize(this.render, out rect.w, out rect.h);

            //var d = SDL.SDL_RenderIsClipEnabled(this.render);
            //SDL.SDL_RenderSetViewport(this.render, ref rect);
            SDL.SDL_SetRenderDrawBlendMode(this.render, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
            SDL.SDL_SetRenderDrawColor(this.render, 0, 0,0, 255);
            //SDL.SDL_RenderClear(this.render); //这个clear有时候有问题,用FillRect替换就稳定了
            SDL.SDL_RenderFillRect(this.render, ref rect);//做一次fillrect 
            this.RootElement?.OnDraw(this);


            //SDL.SDL_Rect srcrect = new SDL.SDL_Rect() { x = 0, y = 0, w = 8, h = 8 };

            //SDL.SDL_RenderCopyEx(this.render, this.tex, ref srcrect, ref rect, 0, IntPtr.Zero, SDL.SDL_RendererFlip.SDL_FLIP_NONE);




            SDL.SDL_RenderPresent(this.render);

        }
        bool bMax;
        public void OnMax(bool max )
        {
            this.bMax = max;
        }
        public void OnResize()
        {
            SDL.SDL_GetWindowSize(windowPtr, out int ww, out int wh);
            //if (wh < 128) wh = 128;
            //if (ww < 256) ww = 256;

            if (this.option.scaleMode == WindowOption_ScaleMode.AutoTileCount)
            {
                var tilex = (int)(ww / (this.option.scale * TilePixelSize));
                var tiley = (int)(wh / (this.option.scale * TilePixelSize));
                bool toosmall = false;
                if (tilex < 5)
                {
                    tilex = 5;
                    toosmall = true;
                }
                if (tiley < 1)
                {
                    tiley = 1;
                    toosmall = true;
                }
                this.option.tileWidth = tilex;
                this.option.tileHeight = tiley;
                if (toosmall)
                {
                    ww = (int)(tilex * this.option.scale * (int)TilePixelSize);
                    wh = (int)(tiley * this.option.scale * (int)TilePixelSize);
                    SDL.SDL_SetWindowSize(windowPtr, ww, wh);
                }
                if (!this.bMax)
                {
                    //取消2次更改窗口尺寸
                    //SDL.SDL_SetWindowSize(windowPtr, ww, wh);
                }//auto resize
                SDL.SDL_RenderSetLogicalSize(render, 0, 0);
                SDL.SDL_RenderSetScale(this.render, this.option.scale, this.option.scale);

                this.RootElement?.OnParentResize(tilex, tiley);
            }
            else if (this.option.scaleMode == WindowOption_ScaleMode.FixTileSize)
            {
                SDL.SDL_RenderSetLogicalSize(render,
                    (int)(this.option.tileWidth * TilePixelSize),
                    (int)(this.option.tileHeight * TilePixelSize));
            }
        }
        public void OnUpdate()
        {
            UpdateWindow();
            eventOnUpdate?.Invoke();
        }
        public void OnEvent(SDL.SDL_WindowEvent _event)
        {
            if (_event.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)
            {
                UpdateWindow();
            }
        }
        public event Action<SDL.SDL_Keysym, bool> eventOnKey;
        public event Action eventOnUpdate;

        public void OnKey(SDL.SDL_Keysym key, bool press)
        {
            eventOnKey?.Invoke(key, press);
            if (elementFocus != null)
                elementFocus.OnKey(this, key.scancode, press);

        }
        /// <summary>
        /// 绘图模式
        /// </summary>
        /// <returns></returns>
        Stack<LimitRect> queueLimit;
        int IWindowRender.GetLimitRectDepth()
        {
            return queueLimit.Count;
        }

        void IWindowRender.PushLimitTileRect(LimitRect rect)
        {
            var currect = (this as IWindowRender).CurrentLimitTileRect;
            if (currect != null)
            {
                if (rect.xmin < currect.Value.xmin) rect.xmin = currect.Value.xmin;
                if (rect.ymin < currect.Value.ymin) rect.ymin = currect.Value.ymin;
                if (rect.xmax > currect.Value.xmax) rect.xmax = currect.Value.xmax;
                if (rect.ymax > currect.Value.ymax) rect.ymax = currect.Value.ymax;
            }
            queueLimit.Push(rect);
            UpdateNewRect();
        }
        void UpdateNewRect()
        {
            if ((this as IWindowRender).CurrentLimitTileRect == null)
            {
                SDL.SDL_RenderSetClipRect(this.render, IntPtr.Zero);
            }
            else
            {
                var newrect = (this as IWindowRender).CurrentLimitTileRect.Value;
                Rect lrect = new Rect()
                {
                    x = newrect.xmin,
                    y = newrect.ymin,
                    w = (newrect.xmax - newrect.xmin),
                    h = (newrect.ymax - newrect.ymin),
                };
                SDL.SDL_RenderSetClipRect(this.render, ref lrect);
            }
        }
        LimitRect? IWindowRender.PopLimitTileRect()
        {
            if (queueLimit.Count == 0)
                return null;
            var rect = queueLimit.Pop();
            UpdateNewRect();
            return rect;
        }
        LimitRect? IWindowRender.CurrentLimitTileRect
        {
            get
            {
                if (queueLimit.Count == 0)
                    return null;
                return queueLimit.Peek();
            }
        }
        SDL.SDL_Rect destrect;
        void IWindowRender.DrawTile(RenderTile tile, int x, int y, byte colorReplace, float scalex, float scaley)
        {
            SDL.SDL_Color color = new SDL.SDL_Color() { r = 255, g = 255, b = 255, a = 255 };
            if (colorReplace > 0 && colorReplace != tile.bank.TransparentColorIndex)
            {
                color = tile.bank.palette[colorReplace];
            }
            else
            {

            }
                (this as IWindowRender).DrawTex(tile.tex, tile.srcrect, x, y, color, scalex, scaley);
        }
        void IWindowRender.DrawTex(IntPtr tex, Rect srcRect, int x, int y, SDL.SDL_Color color, float scalex, float scaley)
        {
            destrect.w = (int)(srcRect.w * scalex);
            destrect.h = (int)(srcRect.h * scaley);
            destrect.x = x;
            destrect.y = y;

            //可以用这个接口改色，但是应限制只能改成bank中的某色
            SDL.SDL_SetTextureColorMod(tex, color.r, color.g, color.b);

            //
            SDL.SDL_RenderCopyEx(this.render, tex, ref srcRect, ref destrect, 0, IntPtr.Zero, SDL.SDL_RendererFlip.SDL_FLIP_NONE);
        }

        void IWindowRender.DrawTileByNames(Bank bank, string[] names, int x, int y, byte colorReplace, float scale)
        {

            foreach (var t in names)
            {
                var tile = bank.GetTileByName(t);
                if (tile == null)
                    continue;
                (this as IWindowRender).DrawTile(tile, x, y, colorReplace, scale, scale);
                int width = (int)(tile.srcrect.w * scale);
                x += width;
            }
        }
        void IWindowRender.DrawTileByNames(Bank bank, IEnumerable<char> names, int x, int y, byte colorReplace, float scale)
        {

            foreach (var t in names)
            {
                var tile = bank.GetTileByName(t.ToString());
                if (tile == null)
                    continue;

                (this as IWindowRender).DrawTile(tile, x, y, colorReplace, scale, scale);
                int width = (int)(tile.srcrect.w * scale);
                x += width;

            }
        }
        public IElementWithEvent elementPointOver
        {
            get;
            private set;
        }
        public IElementWithEvent elementFocus
        {
            get;
            private set;
        }        //event

        IElementWithEvent IWindowEvent.elementPointOver => throw new NotImplementedException();

        IElementWithEvent IWindowEvent.elementFocus => throw new NotImplementedException();

        public void OnMouseDown(byte button)
        {
            if (elementPointOver != null)
            {
                elementPointOver.OnMouseDown(this, button);
            }
            else
            {
                this.RootElement?.OnMouseDown(this, button);
            }
        }
        public void OnMouseUp(byte button)
        {
            //这个事件只给Focus就行了
            if (elementFocus != null)
                elementFocus.OnMouseUp(this, button);
        }
        public void OnMouseMove(int x, int y)
        {
            if (this.option.scaleMode == WindowOption_ScaleMode.AutoTileCount)
            {
                x = (int)(x / this.option.scale);
                y = (int)(y / this.option.scale);
            }
            else if (this.option.scaleMode == WindowOption_ScaleMode.FixTileSize)
            {
                //有修正，基本正确，有一点点偏移？
                SDL.SDL_GetWindowSize(windowPtr, out int winw, out int winh);
                float whs = this.option.tileWidth / this.option.tileHeight;

            }

            if (elementPointOver != null)
            {
                var b = elementPointOver.OnMouseMove(this, x, y);
                if (b)
                    (this as IWindowEvent).RegPointOver(null);
            }


            this.RootElement?.OnMouseMove(this, x, y);//分发事件,第一个抢到的人 设为PointOver，然后截断
        }
        public void OnMouseWheel(uint dir, int x, int y)
        {
            //这个事件只给focus
            if (elementFocus != null)
                elementFocus.OnMouseWheel(this, dir, x, y);
            else if (elementPointOver != null)
            {
                elementPointOver.OnMouseWheel(this, dir, x, y);
            }
        }
        public void OnInput(string text)
        {
            //这个事件只给focus
            if (elementFocus != null)
                elementFocus.OnInput(this, text);

        }

        void IWindowEvent.RegPointOver(IElementWithEvent ele)
        {
            if (this.elementPointOver != null & this.elementPointOver != ele)
                this.elementPointOver.OnMouseLeave(this);
            this.elementPointOver = ele;
        }

        void IWindowEvent.RegFocus(IElementWithEvent ele)
        {
            if (this.elementFocus != null & this.elementFocus != ele)
                this.elementFocus.OnLostFocus(this);

            this.elementFocus = ele;
        }

        void IWindowEvent.SetTextInput(int x1, int y1, int x2, int y2)
        {
            if (this.option.scaleMode == WindowOption_ScaleMode.AutoTileCount)
            {
                x1 = (int)(x1 * this.option.scale);
                y1 = (int)(y1 * this.option.scale);
                x2 = (int)(x2 * this.option.scale);
                y2 = (int)(y2 * this.option.scale);
            }
            else if (this.option.scaleMode == WindowOption_ScaleMode.FixTileSize)
            {
                //有修正，基本正确，有一点点偏移？
                //SDL.SDL_GetWindowSize(windowPtr, out int winw, out int winh);
                //float whs = this.option.tileWidth / this.option.tileHeight;

            }
            SDL.SDL_SetWindowInputFocus(this.windowPtr);
            //比预想值y偏低，为啥？
            Rect rect = new Rect { x = x1, y = y1, w = x2 - x1, h = y2 - y1 };
            SDL.SDL_StartTextInput();
            SDL.SDL_SetTextInputRect(ref rect);

        }

        void IWindowEvent.CloseTextInput()
        {
            SDL.SDL_StopTextInput();
        }
    }
}
